/************************************************************************
* render2d.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#include "graphics.h"
#include "list.h"
#include "array.h"

#include <string.h>
#include <ctype.h>

typedef struct obj2d_s {
	struct obj2d_s *prev;
	struct obj2d_s *next;
	int type;
	rectf_t box;
	material_t *mat;
	textbuffer_t txt;
	textbuffer_t *itxt;
} obj2d_t;

static struct {
	GLuint vao;
	GLuint vbo[2];
	shader_t *shader;
	obj2d_t *stack;
	obj2d_t *current;
} render2d_data = {
	0,
	{0,0},
	NULL,
	NULL,
	NULL
};

static obj2d_t *render2d_section_create()
{
	obj2d_t *r = malloc(sizeof(obj2d_t));
	r->type = RD2_NONE;
	r->mat = NULL;
	r->itxt = NULL;

	textbuffer_init(&r->txt,NULL,0,0,0,0,0,0,0);

	return r;
}

static void render2d_get_section(material_t *m, int t)
{
	if (!render2d_data.current) {
		if (!render2d_data.stack) {
			render2d_data.current = render2d_section_create();
			render2d_data.stack = list_push(&render2d_data.stack,render2d_data.current);
		}else{
			render2d_data.current = render2d_data.stack;
		}
	}

	if (render2d_data.current->type != RD2_NONE) {
		if (
			t == RD2_TEXT
			|| render2d_data.current->type != t
			|| (
				m
				&& m != render2d_data.current->mat
			)
		) {
			if (!render2d_data.current->next) {
				render2d_data.current = render2d_section_create();
				render2d_data.stack = list_push(&render2d_data.stack,render2d_data.current);
			}else{
				render2d_data.current = render2d_data.current->next;
			}
		}
	}
	if (m)
		render2d_data.current->mat = m;

	render2d_data.current->type = t;
}

/* render a 2d line */
void render2d_line(material_t *m, float x, float y, float ex, float ey)
{
	/*
	render2d_get_section(m,RD2_LINE);

	render2d_data.current->box.x = x;
	render2d_data.current->box.y = y;
	render2d_data.current->box.w = ex;
	render2d_data.current->box.h = ey;
	*/
}

/* render a 2d quad of material */
void render2d_quad_mat(material_t *m, float x, float y, float w, float h)
{
	render2d_get_section(m,RD2_QUAD);

	y = wm_data.size.height-y;

	w /= (float)wm_data.size.width;
	h /= (float)wm_data.size.height;
	x = (((x/(float)wm_data.size.width)*2.0)-1.0)+w;
	y = (((y/(float)wm_data.size.height)*2.0)-1.0)-h;

	render2d_data.current->box.x = x;
	render2d_data.current->box.y = y;
	render2d_data.current->box.w = w;
	render2d_data.current->box.h = h;
}

/* render text */
void render2d_text(float x, float y, int w, int h, int font, int size, char* str)
{
	font_t *f;
	render2d_get_section(NULL,RD2_TEXT);

	f = font_get(font);

	textbuffer_adjust(&render2d_data.current->txt,f,size,x,y,w,h,0,0);
	textbuffer_addstr(&render2d_data.current->txt,str);
}

/* render a pregenerated textbuffer */
void render2d_textbuffer(textbuffer_t *t)
{
	float x;
	float y;
	float s;

	if (!t || !t->length)
		return;

	render2d_get_section(NULL,RD2_TEXT);

	render2d_data.current->itxt = t;

	s = (((float)t->font_size)*0.15);
	s /= (float)wm_data.size.height;

	x = t->x;
	y = wm_data.size.height-t->y;

	x = (((x/(float)wm_data.size.width)*2.0)-1.0);
	y = (((y/(float)wm_data.size.height)*2.0)-1.0);

	render2d_data.current->box.x = x;
	render2d_data.current->box.y = y;
	render2d_data.current->box.w = s;
	render2d_data.current->box.h = s;
}

/* render a textbuffer */

/* render 2d graphics to the frame */
void render2d()
{
	int s = 0;
	int b = 0;
	int i;
	rendertext_t *t;
	textbuffer_t *txt;
	matrix_t m;

	glDisable(GL_DEPTH_TEST);

	render2d_data.current = render2d_data.stack;

	if (render2d_data.vao == 0) {
		GLfloat vertices[8] = {-1.0,1.0, -1.0,-1.0, 1.0,1.0, 1.0,-1.0};
		GLfloat texcoords[8] = {0.0,0.0, 0.0,1.0, 1.0,0.0, 1.0,1.0};
		glGenVertexArrays(1,&render2d_data.vao);
		glBindVertexArray(render2d_data.vao);
		glGenBuffers(2, render2d_data.vbo);
		glBindBuffer(GL_ARRAY_BUFFER, render2d_data.vbo[0]);
		glBufferData(GL_ARRAY_BUFFER, 32, vertices, GL_STATIC_DRAW);
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,0,0);
		glBindBuffer(GL_ARRAY_BUFFER, render2d_data.vbo[1]);
		glBufferData(GL_ARRAY_BUFFER, 32, texcoords, GL_STATIC_DRAW);
		glEnableVertexAttribArray(1);
		glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}

	/* shader */
	if (!render2d_data.shader) {
		render2d_data.shader = shader_create("ui");
		shader_attribute(render2d_data.shader,0,"position");
		shader_attribute(render2d_data.shader,1,"uvs");
	}

	shader_enable(render2d_data.shader);

	glBindVertexArray(render2d_data.vao);
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);
	b = 0;

	while (render2d_data.current && render2d_data.current->type != RD2_NONE) {
		if (render2d_data.current->type == RD2_TEXT) {
			txt = render2d_data.current->itxt;
			if (!txt)
				txt = &render2d_data.current->txt;
			for (i=0; i<txt->data.length; i++) {
				t = array_get_ptr(&txt->data,i);
				switch (t->state) {
				case 0:
					glGenVertexArrays(1,&t->vao);
					glBindVertexArray(t->vao);
					glGenBuffers(2, t->vbo);
					glBindBuffer(GL_ARRAY_BUFFER, t->vbo[0]);
					glBufferData(GL_ARRAY_BUFFER, t->v.length*4, t->v.data, GL_STATIC_DRAW);
					glEnableVertexAttribArray(0);
					glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,0,0);
					glBindBuffer(GL_ARRAY_BUFFER, t->vbo[1]);
					glBufferData(GL_ARRAY_BUFFER, t->t.length*4, t->t.data, GL_STATIC_DRAW);
					glEnableVertexAttribArray(1);
					glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,0);
					t->state = 1;
					break;
				case 1:
					glBindVertexArray(t->vao);
					glEnableVertexAttribArray(0);
					glEnableVertexAttribArray(1);
					break;
				case 2:
					glDeleteBuffers(2, t->vbo);
					glGenBuffers(2, t->vbo);
					glBindBuffer(GL_ARRAY_BUFFER, t->vbo[0]);
					glBufferData(GL_ARRAY_BUFFER, t->v.length*4, t->v.data, GL_STATIC_DRAW);
					glEnableVertexAttribArray(0);
					glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,0,0);
					glBindBuffer(GL_ARRAY_BUFFER, t->vbo[1]);
					glBufferData(GL_ARRAY_BUFFER, t->t.length*4, t->t.data, GL_STATIC_DRAW);
					glEnableVertexAttribArray(1);
					glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,0);
					t->state = 1;
					break;
				default:;
				}
				mat_bind_with_opts(t->glid,MATOPT_ALPHA_BLEND|MATOPT_SDF_ALPHA);
				matrix_init(&m);
				matrix_scale(&m,render2d_data.current->box.w,render2d_data.current->box.h,1.0);
				matrix_translate(&m,render2d_data.current->box.x,render2d_data.current->box.y,1.0);
				shader_uniform_matrix(render2d_data.shader,"transformationMatrix",&m);
				glDrawArrays(GL_TRIANGLES,0,t->v.length/2);
			}
			b = 1;
			render2d_data.current->itxt = NULL;
			textbuffer_clear(&render2d_data.current->txt);
		}else if (render2d_data.current->type == RD2_QUAD) {
			if (b != 0) {
				glBindVertexArray(render2d_data.vao);
				glEnableVertexAttribArray(0);
				glEnableVertexAttribArray(1);
				b = 0;
			}
			mat_use(render2d_data.current->mat,render2d_data.shader);

			matrix_init(&m);
			matrix_scale(&m,render2d_data.current->box.w,render2d_data.current->box.h,1.0);
			matrix_translate(&m,render2d_data.current->box.x,render2d_data.current->box.y,1.0);
			shader_uniform_matrix(render2d_data.shader,"transformationMatrix",&m);

			glDrawArrays(GL_TRIANGLE_STRIP,0,4);
			render2d_data.current->mat = NULL;
		}

		render2d_data.current->type = RD2_NONE;
		render2d_data.current = render2d_data.current->next;
		s++;
	}

	glDisableVertexAttribArray(0);
	glDisableVertexAttribArray(1);
	glBindVertexArray(0);

	/* shader */
	shader_disable(render2d_data.shader);

	render2d_data.current = NULL;
}
